//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using OpenLS.Core;
using OpenLS.Spreadsheet.Expressions;
using OpenLS.Spreadsheet.Extensibility;

namespace OpenLS.Spreadsheet
{
    public class MultiCellsRange : CellsRange
    {
        private readonly Worksheet _worksheet;
        private RangeAddress _address;

        public override int Column
        {
            get { return Address.TopLeft.Column; }
        }

        public override int Row
        {
            get { return Address.TopLeft.Row; }
        }

        public override string Label
        {
            get { return Address.Label; //\\ return _address.TopLeft.Label + ":" + _address.BottomRight.Label;
            }
        }

        public override Worksheet Worksheet
        {
            get { return _worksheet; }
        }

        internal override RangeAddress Address
        {
            get { return _address; }
        }

        public override int ColumnSpan
        {
            get { return Address.ColumnSpan; }
        }

        public override int RowSpan
        {
            get { return Address.RowSpan; }
        }

        internal override Cell FirstCell
        {
            get { return TopLeftCell; }
        }

        public override Cell this[int row, int column]
        {
            get { return Worksheet[Row + row, Column + column]; }
        }

        #region navigation

        public override Cell TopLeftCell
        {
            get { return Worksheet[Address.TopLeft]; }
        }

        public override Cell BottomLeft
        {
            get { return Worksheet[Address.BottomLeft]; }
        }

        public override Cell TopRight
        {
            get { return Worksheet[Address.TopRight]; }
        }

        public override Cell BottomRight
        {
            get { return Worksheet[Address.BottomRight]; }
        }

        public override IEnumerable<Column> GetColumns()
        {
            for (int i = Address.TopLeft.Column; i <= Address.BottomRight.Column; i++)
                yield return Worksheet.Columns[i];
        }

        public override IEnumerable<Row> GetRows()
        {
            for (int i = Address.TopLeft.Row; i <= Address.BottomRight.Row; i++)
                yield return Worksheet.Rows[i];
        }

        internal override IEnumerable<Row> GetNonEmptyRows()
        {
            int min = Address.TopLeft.Row;
            int max = Address.BottomRight.Row;
            foreach (Row r in Worksheet.Rows)
            {
                if (r.Index < min)
                    continue;
                if (r.Index > max)
                    break;
                yield return r;
            }
        }

        internal override IEnumerable<Cell> GetLeftCells()
        {
            CellAddress a = Address.TopLeft;
            while (true)
            {
                yield return Worksheet[a];
                if (a.Equals(Address.BottomLeft))
                    break;
                a = a.GetNeighbor(NavigationDirection.Down);
            }
        }

        internal override IEnumerable<Cell> GetRightCells()
        {
            CellAddress a = Address.TopRight;
            while (true)
            {
                yield return Worksheet[a];
                if (a.Equals(Address.BottomRight))
                    break;
                a = a.GetNeighbor(NavigationDirection.Down);
            }
        }

        internal override IEnumerable<Cell> GetTopCells()
        {
            CellAddress a = Address.TopLeft;
            while (true)
            {
                yield return Worksheet[a];
                if (a.Equals(Address.TopRight))
                    break;
                a = a.GetNeighbor(NavigationDirection.Right);
            }
        }

        internal override IEnumerable<Cell> GetBottomCells()
        {
            CellAddress a = Address.BottomLeft;
            while (true)
            {
                yield return Worksheet[a];
                if (a.Equals(Address.BottomRight))
                    break;
                a = a.GetNeighbor(NavigationDirection.Right);
            }
        }

        public IEnumerable<Cell> GetEnumerator()
        {
            if (Address.Surface < Worksheet.NonVolatileCells.Count)
            {
                var dictionary = new Dictionary<CellAddress, Cell>(new CellAddressComparer());
                foreach (CellAddress a in Address.GetCellAddresses())
                {
                    Cell cell = Worksheet.NonVolatileCells.GetItemIfAny(a);
                    if (cell != null && !dictionary.ContainsKey(cell.Address))
                        dictionary.Add(cell.Address, cell);
                }
                foreach (Cell c in dictionary.Values)
                    yield return c;
            }
            else
            {
                foreach (Cell cell in Worksheet.NonVolatileCells)
                {
                    if (Contains(cell))
                        yield return cell;
                }
            }
        }

        internal override Border GetBorder(BorderType borderType, out bool uniform)
        {
            List<Border> borders = Enumerable.ToList(Enumerable.Take(Enumerable.Distinct(GetBorders(borderType)), 2));
            switch (borders.Count)
            {
                case 0:
                    uniform = true;
                    return null;
                case 1:
                    uniform = true;
                    return borders[0];
                default:
                    uniform = false;
                    return null;
            }
        }

        private IEnumerable<Border> GetBorders(BorderType borderType)
        {
            switch (borderType)
            {
                case BorderType.BottomEdge:
                case BorderType.LeftEdge:
                case BorderType.RightEdge:
                case BorderType.TopEdge:
                    return Enumerable.Select(GetBorderCells(borderType),
                                             c =>
                                             c.ActualBorders != null
                                                 ? c.ActualBorders.GetBorder(borderType)
                                                 : null);
                case BorderType.DiagonalDown:
                case BorderType.DiagonalUp:
                    {
                        IEnumerable<Borders> rowBorders = Enumerable.Select(GetNonEmptyRows(), r => r.Borders);
                        IEnumerable<Borders> columnBorders = Enumerable.Select(GetNonEmptyColumns(), r => r.Borders);
                        IEnumerable<Borders> cellBorders = Enumerable.Select(GetNonEmptyCells(), r => r.ActualBorders);
                        IEnumerable<Borders> allBorders = Enumerable.Concat(
                            Enumerable.Concat(rowBorders, columnBorders), cellBorders);
                        return Enumerable.Select(allBorders, b => b != null ? b.GetBorder(borderType) : null);
                    }
                case BorderType.InsideHorizontal:
                    {
                        var pairs = new List<CellPair>();
                        for (int row = Row; row < Row + RowSpan - 1; row++)
                        {
                            for (int column = Column; column < Column + ColumnSpan; column++)
                            {
                                pairs.Add(new CellPair(Worksheet[row, column], Worksheet[row + 1, column]));
                            }
                        }
                        return Enumerable.SelectMany(pairs, pair => pair.GetHorizontalBorders());
                    }
                case BorderType.InsideVertical:
                    {
                        var pairs = new List<CellPair>();
                        for (int row = Row; row < Row + RowSpan; row++)
                        {
                            for (int column = Column; column < Column + ColumnSpan - 1; column++)
                            {
                                pairs.Add(new CellPair(Worksheet[row, column], Worksheet[row, column + 1]));
                            }
                        }
                        return Enumerable.SelectMany(pairs, pair => pair.GetVerticalBorders());
                    }
                case BorderType.All:
                case BorderType.OutsideEdge:
                default:
                    throw new NotSupportedException();
            }
        }

        public override IEnumerable<Cell> GetAllCells()
        {
            foreach (CellAddress a in Address.GetCellAddresses())
                yield return Worksheet[a];
        }

        #endregion

        #region ctor

        internal MultiCellsRange(Worksheet owner, RangeAddress address, RangeType type)
            : this(owner, address, type, true)
        {
        }
        internal MultiCellsRange(Worksheet owner, RangeAddress address)
            : this(owner, address, RangeType.Dynamic)
        {
        }

        internal MultiCellsRange(Cell topLeft, Cell bottomRight)
            : this(topLeft.Worksheet, new RangeAddress(topLeft.Address, bottomRight.Address))
        {
            Contract.Requires(topLeft != null);
            Contract.Requires(bottomRight != null);
            Contract.Requires((bottomRight.Address - topLeft.Address).Row + 1 != Spreadsheet.Row.MaxSpan);
            Contract.Requires((bottomRight.Address - topLeft.Address).Column + 1 != Spreadsheet.Column.MaxSpan);
            Contract.Ensures((Label != "A1:A1048576"));

            if ((bottomRight.Address - topLeft.Address).Row == Spreadsheet.Row.MaxSpan)
                Debug.WriteLine("pb");
            if ((bottomRight.Address - topLeft.Address).Column == Spreadsheet.Column.MaxSpan)
                Debug.WriteLine("pb");
        }

        internal MultiCellsRange(Worksheet owner, RangeAddress address, RangeType type, bool adjustForSpans)
        {
            Contract.Requires(!(address.TopLeft.Column == 0 && address.BottomRight.Column == Worksheet.MaxColumnIndex));
            Contract.Requires(!(address.TopLeft.Row == 0 && address.BottomRight.Row == Worksheet.MaxRowIndex));
            Contract.Ensures((BottomRight.Address - TopLeftCell.Address).Row != Spreadsheet.Row.MaxSpan);
            Contract.Ensures((BottomRight.Address - TopLeftCell.Address).Row != Spreadsheet.Row.MaxSpan);
            Contract.Ensures((Label != "A1:A1048576"));

            _worksheet = owner;
            _address = address;

            if (adjustForSpans)
                while (this.adjustForSpans()) ;
            if (type == RangeType.Dynamic)
                _worksheet.AddWeakRangeChanged(OnRangeChanged);
        }

        private void OnRangeChanged(object sender, RangeChangeEventArgs e)
        {
            if (sender == this)
                return;
            if (this.Worksheet.Workbook.UndoManager.State == UndoState.Undo)
                return;
            switch (e.Type)
            {
                case RangeChangeType.Deletion:
                    {
                        switch (e.Hint)
                        {
                            case CellMoveOrientationHint.Vertical:
                                {
                                    if (e.Address.TopLeft.Column > Address.TopLeft.Column || e.Address.TopRight.Column < Address.TopRight.Column)
                                        return;
                                    int otherStart = e.Address.TopLeft.Row;
                                    int otherEnd = e.Address.BottomRight.Row;
                                    int myStart = Address.TopLeft.Row;
                                    int myEnd = Address.BottomRight.Row;
                                    if (otherEnd < myStart)
                                        SetAddress(Address + new CellRelativeAddress(otherStart - otherEnd - 1, 0));
                                    else if (otherStart > myEnd)
                                    {
                                        // Nothing to do
                                    }
                                    else if (otherStart <= myStart && otherEnd < myEnd)
                                    {
                                        SetAddress(new RangeAddress(new CellAddress(otherStart, Address.TopLeft.Column),
                                                                    new CellAddress(otherStart + myEnd - otherEnd - 1,
                                                                                    Address.BottomRight.Column)));
                                    }
                                    else if (otherStart > myStart && otherEnd < myEnd)
                                    {
                                        SetAddress(new RangeAddress(Address.TopLeft,
                                                                    Address.BottomRight -
                                                                    new CellRelativeAddress(otherEnd - otherStart + 1, 0)));
                                    }
                                    else if (otherStart > myStart && otherStart <= myEnd && otherEnd >= myEnd)
                                    {
                                        SetAddress(new RangeAddress(Address.TopLeft,
                                                                    new CellAddress(otherStart - 1,
                                                                                    Address.BottomRight.Column)));
                                    }
                                    else
                                    {
                                        // make invalid
                                    }
                                }
                                break;
                            case CellMoveOrientationHint.Horizontal:
                                {
                                    if (e.Address.TopLeft.Row > Address.TopLeft.Row || e.Address.BottomRight.Row < Address.BottomRight.Row)
                                        return;
                                   
                                    int otherStart = e.Address.TopLeft.Column;
                                    int otherEnd = e.Address.BottomRight.Column;
                                    int myStart = Address.TopLeft.Column;
                                    int myEnd = Address.BottomRight.Column;
                                    if (otherEnd < myStart)
                                        SetAddress(Address + new CellRelativeAddress(0, otherStart - otherEnd - 1));
                                    else if (otherStart > myEnd)
                                    {
                                        // Nothing to do
                                    }
                                    else if (otherStart <= myStart && otherEnd < myEnd)
                                    {
                                        SetAddress(new RangeAddress(new CellAddress(Address.TopLeft.Row, otherStart),
                                                                    new CellAddress(Address.BottomRight.Row,
                                                                                    otherStart + myEnd - otherEnd - 1)));
                                    }
                                    else if (otherStart > myStart && otherEnd < myEnd)
                                    {
                                        SetAddress(new RangeAddress(Address.TopLeft,
                                                                    Address.BottomRight -
                                                                    new CellRelativeAddress(0, otherEnd - otherStart + 1)));
                                    }
                                    else if (otherStart > myStart && otherStart <= myEnd && otherEnd >= myEnd)
                                    {
                                        SetAddress(new RangeAddress(Address.TopLeft,
                                                                    new CellAddress(Address.BottomRight.Row,
                                                                                    otherStart - 1)));
                                    }
                                    else
                                    {
                                        // make invalid
                                    }
                                }
                                break;
                        }
                    }
                    break;
                case RangeChangeType.Insertion:
                    switch (e.Hint)
                    {
                        case CellMoveOrientationHint.Vertical:
                            {
                                if (e.Address.TopLeft.Column > Address.TopLeft.Column || e.Address.BottomRight.Column < Address.BottomRight.Column)
                                    return;

                                int topCompare = e.Address.TopLeft.Row.CompareTo(Address.TopLeft.Row);
                                int topBottomCompare = e.Address.TopLeft.Row.CompareTo(Address.BottomLeft.Row);
                                int bottomCompare = e.Address.BottomLeft.Row.CompareTo(Address.BottomLeft.Row);
                                switch (topCompare)
                                {
                                    case -1:
                                    case 0:
                                        SetAddress(Address + new CellRelativeAddress(e.Address.RowSpan, 0));

                                        break;
                                    case 1:
                                        switch (bottomCompare)
                                        {
                                            case -1:
                                            case 0:
                                                SetAddress(new RangeAddress(Address.TopLeft,
                                                                            Address.BottomRight +
                                                                            new CellRelativeAddress(e.Address.RowSpan, 0)));
                                                break;
                                            case 1:
                                                if (topBottomCompare < 0)
                                                    SetAddress(new RangeAddress(Address.TopLeft,
                                                                                Address.BottomRight +
                                                                                new CellRelativeAddress(
                                                                                    e.Address.RowSpan, 0)));
                                                break;
                                        }
                                        break;
                                }
                            }
                            break;

                        case CellMoveOrientationHint.Horizontal:
                            {
                                if (e.Address.TopLeft.Row > Address.TopLeft.Row || e.Address.BottomRight.Row < Address.BottomRight.Row)
                                    return;

                                int topCompare = e.Address.BottomRight.Column.CompareTo(Address.TopLeft.Column);
                                int topBottomCompare = e.Address.TopLeft.Column.CompareTo(Address.BottomRight.Column);
                                int bottomCompare = e.Address.BottomRight.Column.CompareTo(Address.BottomRight.Column);
                                switch (topCompare)
                                {
                                    case -1:
                                    case 0:
                                        SetAddress(Address + new CellRelativeAddress(0, e.Address.ColumnSpan));

                                        break;
                                    case 1:
                                        switch (bottomCompare)
                                        {
                                            case -1:
                                            case 0:
                                                SetAddress(new RangeAddress(Address.TopLeft,
                                                                            Address.BottomRight +
                                                                            new CellRelativeAddress(0,
                                                                                                    e.Address.ColumnSpan)));
                                                break;
                                            case 1:
                                                if (topBottomCompare < 0)
                                                    SetAddress(new RangeAddress(Address.TopLeft,
                                                                                Address.BottomRight +
                                                                                new CellRelativeAddress(0,
                                                                                                        e.Address.
                                                                                                            ColumnSpan)));
                                                break;
                                        }
                                        break;
                                }
                            }
                            break;
                        default:
                            throw new NotSupportedException();
                    }
                    break;
            }

        }

        internal RangeAddress? GetNewRangeAddress(RangeChangeType type, CellMoveOrientationHint hint, RangeAddress address)
        {
            switch (type)
            {
                case RangeChangeType.Deletion:
                    {
                        switch (hint)
                        {
                            case CellMoveOrientationHint.Vertical:
                                {
                                    if (address.TopLeft.Column > Address.TopLeft.Column || address.TopRight.Column < Address.TopRight.Column)
                                        return null;
                                    int otherStart = address.TopLeft.Row;
                                    int otherEnd = address.BottomRight.Row;
                                    int myStart = Address.TopLeft.Row;
                                    int myEnd = Address.BottomRight.Row;
                                    if (otherEnd < myStart)
                                        return(Address + new CellRelativeAddress(otherStart - otherEnd - 1, 0));
                                    else if (otherStart > myEnd)
                                    {
                                        return null;
                                    }
                                    else if (otherStart <= myStart && otherEnd < myEnd)
                                    {
                                        return(new RangeAddress(new CellAddress(otherStart, Address.TopLeft.Column),
                                                                    new CellAddress(otherStart + myEnd - otherEnd - 1,
                                                                                    Address.BottomRight.Column)));
                                    }
                                    else if (otherStart > myStart && otherEnd < myEnd)
                                    {
                                        return(new RangeAddress(Address.TopLeft,
                                                                    Address.BottomRight -
                                                                    new CellRelativeAddress(otherEnd - otherStart + 1, 0)));
                                    }
                                    else if (otherStart > myStart && otherStart <= myEnd && otherEnd >= myEnd)
                                    {
                                        return(new RangeAddress(Address.TopLeft,
                                                                    new CellAddress(otherStart - 1,
                                                                                    Address.BottomRight.Column)));
                                    }
                                    else
                                    {
                                        return null;
                                    }
                                }
                                break;
                            case CellMoveOrientationHint.Horizontal:
                                {
                                    if (address.TopLeft.Row > Address.TopLeft.Row || address.BottomRight.Row < Address.BottomRight.Row)
                                        return null;

                                    int otherStart = address.TopLeft.Column;
                                    int otherEnd = address.BottomRight.Column;
                                    int myStart = Address.TopLeft.Column;
                                    int myEnd = Address.BottomRight.Column;
                                    if (otherEnd < myStart)
                                        return (Address + new CellRelativeAddress(0, otherStart - otherEnd - 1));
                                    else if (otherStart > myEnd)
                                    {
                                        return null;
                                    }
                                    else if (otherStart <= myStart && otherEnd < myEnd)
                                    {
                                        return(new RangeAddress(new CellAddress(Address.TopLeft.Row, otherStart),
                                                                    new CellAddress(Address.BottomRight.Row,
                                                                                    otherStart + myEnd - otherEnd - 1)));
                                    }
                                    else if (otherStart > myStart && otherEnd < myEnd)
                                    {
                                        return (new RangeAddress(Address.TopLeft,
                                                                    Address.BottomRight -
                                                                    new CellRelativeAddress(0, otherEnd - otherStart + 1)));
                                    }
                                    else if (otherStart > myStart && otherStart <= myEnd && otherEnd >= myEnd)
                                    {
                                        return (new RangeAddress(Address.TopLeft,
                                                                    new CellAddress(Address.BottomRight.Row,
                                                                                    otherStart - 1)));
                                    }
                                    else
                                    {
                                        return null;
                                    }
                                }
                                break;
                        }
                    }
                    break;
                case RangeChangeType.Insertion:
                    switch (hint)
                    {
                        case CellMoveOrientationHint.Vertical:
                            {
                                if (address.TopLeft.Column > Address.TopLeft.Column || address.BottomRight.Column < Address.BottomRight.Column)
                                    return null;

                                int topCompare = address.TopLeft.Row.CompareTo(Address.TopLeft.Row);
                                int topBottomCompare = address.TopLeft.Row.CompareTo(Address.BottomLeft.Row);
                                int bottomCompare = address.BottomLeft.Row.CompareTo(Address.BottomLeft.Row);
                                switch (topCompare)
                                {
                                    case -1:
                                    case 0:
                                        return (Address + new CellRelativeAddress(address.RowSpan, 0));

                                        break;
                                    case 1:
                                        switch (bottomCompare)
                                        {
                                            case -1:
                                            case 0:
                                                return (new RangeAddress(Address.TopLeft,
                                                                            Address.BottomRight +
                                                                            new CellRelativeAddress(address.RowSpan, 0)));
                                                break;
                                            case 1:
                                                if (topBottomCompare < 0)
                                                    return (new RangeAddress(Address.TopLeft,
                                                                                Address.BottomRight +
                                                                                new CellRelativeAddress(
                                                                                    address.RowSpan, 0)));
                                                break;
                                        }
                                        break;
                                }
                            }
                            break;

                        case CellMoveOrientationHint.Horizontal:
                            {
                                if (address.TopLeft.Row > Address.TopLeft.Row || address.BottomRight.Row < Address.BottomRight.Row)
                                    return null ;

                                int topCompare = address.BottomRight.Column.CompareTo(Address.TopLeft.Column);
                                int topBottomCompare = address.TopLeft.Column.CompareTo(Address.BottomRight.Column);
                                int bottomCompare = address.BottomRight.Column.CompareTo(Address.BottomRight.Column);
                                switch (topCompare)
                                {
                                    case -1:
                                    case 0:
                                        return (Address + new CellRelativeAddress(0, address.ColumnSpan));

                                        break;
                                    case 1:
                                        switch (bottomCompare)
                                        {
                                            case -1:
                                            case 0:
                                                return (new RangeAddress(Address.TopLeft,
                                                                            Address.BottomRight +
                                                                            new CellRelativeAddress(0,
                                                                                                    address.ColumnSpan)));
                                                break;
                                            case 1:
                                                if (topBottomCompare < 0)
                                                    return (new RangeAddress(Address.TopLeft,
                                                                                Address.BottomRight +
                                                                                new CellRelativeAddress(0,
                                                                                                        address.
                                                                                                            ColumnSpan)));
                                                break;
                                        }
                                        break;
                                }
                            }
                            break;
                        default:
                            throw new NotSupportedException();
                    }
                    break;
            }
            return null;

        }

        private bool adjustForSpans() //\\
        {
            bool result = false;
            int surface = Address.ColumnSpan*Address.RowSpan;
            int cellCount = Worksheet.NonVolatileCells.Count;
            if (surface < cellCount)
            {
                foreach (CellAddress a in Address.GetCellAddresses())
                {
                    Cell cell = Worksheet.NonVolatileCells.GetItemIfAny(a);
                    if (cell != null && cell.HasSpan)
                    {
                        if (!Address.Contains(cell.GetBottomRight()))
                        {
                            _address = Address.Extend(cell.GetBottomRight());
                            result = true;
                        }
                    }
                }
            }
            else
            {
                foreach (Cell cell in Enumerable.Where(Worksheet.NonVolatileCells, c => c != null && c.HasSpan))
                {
                    var cellRangeAddress = new RangeAddress(cell.Address, cell.GetBottomRight());
                    if (cellRangeAddress.Intersects(Address))
                    {
                        if (!Address.Contains(cell.GetBottomRight()))
                        {
                            _address = Address.Extend(cell.GetBottomRight());
                            result = true;
                        }
                    }
                }
            }
            return result;
        }

        #endregion

        private void SetAddress(RangeAddress value)
        {
            if (_address.Equals(value))
                return;
            Worksheet.Workbook.AddUndoAction(_address, SetAddress);
            _address = value;
        }

        internal override IEnumerable<Row> GetExistingRows()
        {
            if (RowSpan > Worksheet.Rows.Count)
            {
                return Enumerable.Where(Worksheet.Rows,
                                        r => r.Index >= Address.TopLeft.Row && r.Index <= Address.BottomRight.Row);
            }
            return base.GetExistingRows();
        }

        internal override IEnumerable<Column> GetExistingColumns()
        {
            if (RowSpan > Worksheet.Columns.Count)
            {
                return Enumerable.Where(Worksheet.Columns,
                                        r =>
                                        r.Index >= Address.TopLeft.Column && r.Index <= Address.BottomRight.Column);
            }
            return base.GetExistingColumns();
        }

        internal override object GetFirstCellValue(CellProperty p)
        {
            return TopLeftCell.GetCellValue(p);
        }

        internal override RangeReference ToReference()
        {
            return new MultiCellsRangeReference(new StaticCellReference(new AnchoredRowReference(TopLeftCell.Row, true),
                                                                        new AnchoredColumnReference(TopLeftCell.Column,
                                                                                                    true)),
                                                new StaticCellReference(new AnchoredRowReference(BottomRight.Row, true),
                                                                        new AnchoredColumnReference(BottomRight.Column,
                                                                                                    true)));
        }

        internal override SimpleRange ProjectedSimple(Worksheet worksheet, CellRelativeAddress diff)
        {
            return new MultiCellsRange(worksheet, Address + diff);
        }

        internal override IEnumerable<PartialRowOrColumnRange> GetPartialRowOrColumnRanges(RowOrColumn rc)
        {
            var cells = new List<Cell>(GetNonEmptyCells());
            cells.Sort(new Comparison<Cell>(delegate(Cell c1, Cell c2)
                                                {
                                                    int i;
                                                    switch (rc)
                                                    {
                                                        case RowOrColumn.Row:
                                                            i = c1.Row.Index.CompareTo(c2.Row.Index);
                                                            if (i != 0)
                                                                return i;
                                                            return c2.Column.Index.CompareTo(c2.Column.Index);
                                                        case RowOrColumn.Column:
                                                            i = c1.Column.Index.CompareTo(c2.Column.Index);
                                                            if (i != 0)
                                                                return i;
                                                            return c2.Row.Index.CompareTo(c2.Row.Index);
                                                        default:
                                                            throw new NotSupportedException();
                                                    }
                                                }));
            Cell previousCell = null;
            foreach (Cell cell in cells)
            {
                switch (rc)
                {
                    case RowOrColumn.Row:
                        if (previousCell == null || previousCell.Row != cell.Row)
                        {
                            yield return
                                new PartialRowOrColumnRange(Worksheet,
                                                            new CellAddress(cell.Row.Index, TopLeftCell.Column.Index),
                                                            ColumnSpan, rc);
                        }
                        break;
                    case RowOrColumn.Column:
                        if (previousCell == null || previousCell.Column != cell.Column)
                        {
                            yield return
                                new PartialRowOrColumnRange(Worksheet,
                                                            new CellAddress(TopLeftCell.Row.Index, cell.Column.Index),
                                                            RowSpan, rc);
                        }
                        break;
                }
                previousCell = cell;
            }
        }

        internal override CellFormattingRecord GetFormattingRecord()
        {
            return TopLeftCell.FormattingRecord;
        }

        internal override IEnumerable<CellFormattingRecord> GetFormattingRecords()
        {
            foreach (Cell cell in GetNonEmptyCells())
                yield return cell.FormattingRecord;
        }

        internal FullColumnRange ToFullColumnRange()
        {
            return new FullColumnRange(TopLeftCell.Column, BottomRight.Column);
        }

        internal bool Intersects(CellsRange other)
        {
            if (Worksheet != other.Worksheet)
                return false;
            return Address.Intersects(other.Address);
        }

        /*System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }*/
        //\\

        internal override void SetCellValueNoUndo(CellProperty p, object value)
        {
            foreach (Cell c in GetAllCells())
                c.SetCellValue(p, value);
        }

        internal override object GetCellValue(CellProperty p, out bool uniform)
        {
            object result = null;
            bool first = true;
            uniform = true;
            foreach (Cell c in GetNonEmptyCells()) //\\inefficient
            {
                object t = c.GetCellValue(p);
                if (!first)
                {
                    if (!Equals(t, result))
                    {
                        uniform = false;
                        return t;
                    }
                }
                else
                    first = false;
                result = t;
            }
            return result;
        }

        internal override IEnumerable<Cell> GetNonEmptyCells()
        {
            foreach (Cell c in GetEnumerator())
                yield return c;
        }


        internal override bool Contains(CellAddress cellAddress)
        {
            return Address.Contains(cellAddress);
        }

        internal override void SetStyleNoUndo(CellStyle s)
        {
            foreach (Cell c in GetAllCells())
                c.Style = (s);
        }

        internal override Cell GetNextCell(Cell cell, CellNavigationDirection direction)
        {
            switch (direction)
            {
                case CellNavigationDirection.Forward:
                    if (cell == BottomRight)
                        return TopLeftCell;
                    if (cell.Column.Index == BottomRight.Column.Index)
                        return Worksheet[cell.Row.Index + 1, TopLeftCell.Column.Index];
                    return Worksheet[cell.Row.Index, cell.Column.Index + 1];
                case CellNavigationDirection.Backward:

                    if (cell == TopLeftCell)
                        return BottomRight;
                    if (cell.Column.Index == Address.TopLeft.Column)
                        return Worksheet[cell.Row.Index - 1, BottomRight.Column.Index];
                    return Worksheet[cell.Row.Index, cell.Column.Index - 1];
                default:
                    throw new NotSupportedException();
            }
        }

        internal override Range GetColumnRange(int index, RangeType type)
        {
            if (RowSpan == 1)
                return new SingleCellRange(TopLeftCell);
            return new MultiCellsRange(Worksheet, new RangeAddress(
                                                      TopLeftCell.Address + new CellRelativeAddress(0, index),
                                                      BottomLeft.Address + new CellRelativeAddress(0, index)));
        }

        private IEnumerable<Cell> GetBorderCells(BorderType borderType)
        {
            switch (borderType)
            {
                case BorderType.LeftEdge:
                    return GetLeftCells();
                case BorderType.TopEdge:
                    return GetTopCells();
                case BorderType.RightEdge:
                    return GetRightCells();
                case BorderType.BottomEdge:
                    return GetBottomCells();
                default:
                    throw new NotSupportedException();
            }
        }

        /// <summary>
        /// Set the border for a given range edge
        /// </summary>
        /// <param name="border">The value of the border to set</param>
        /// <param name="borderType">The type of border to set</param>
        internal override void SetBorderNoUndo(Border border, BorderType borderType)
        {
            if ((borderType & BorderType.LeftEdge) != 0)
                foreach (Cell cell in GetLeftCells())
                {
                    cell.SetBorder(border, BorderType.LeftEdge);
                }

            if ((borderType & BorderType.TopEdge) != 0)
                foreach (Cell cell in GetTopCells())
                {
                    cell.SetBorder(border, BorderType.TopEdge);
                }

            if ((borderType & BorderType.RightEdge) != 0)
                foreach (Cell cell in GetRightCells())
                {
                    cell.SetBorder(border, BorderType.RightEdge);
                }

            if ((borderType & BorderType.BottomEdge) != 0)
                foreach (Cell cell in GetBottomCells())
                {
                    cell.SetBorder(border, BorderType.BottomEdge);
                }
            if ((borderType & BorderType.InsideHorizontal) != 0)
                foreach (Cell cell in GetAllCells())
                {
                    if (cell.Row.Index != Row)
                        cell.SetBorder(border, BorderType.TopEdge);
                    if (cell.Row.Index + cell.RowSpan != Row + RowSpan)
                        cell.SetBorder(border, BorderType.BottomEdge);
                }
            if ((borderType & BorderType.InsideVertical) != 0)
                foreach (Cell cell in GetAllCells())
                {
                    if (cell.Column.Index != Column)
                        cell.SetBorder(border, BorderType.LeftEdge);
                    if (cell.Column.Index + cell.ColumnSpan != Column + ColumnSpan)
                        cell.SetBorder(border, BorderType.RightEdge);
                }
        }

        internal override Range GetRowRange(int index, RangeType type)
        {
            // if (ColumnSpan == 1)
            //    return new SingleCellRange(TopLeftCell);
            return new MultiCellsRange(Worksheet, new RangeAddress(
                                                      TopLeftCell.Address + new CellRelativeAddress(index, 0),
                                                      TopRight.Address + new CellRelativeAddress(index, 0)), type);
        }

        internal override Range Offseted(CellRelativeAddress address)
        {
            return new MultiCellsRange(TopLeftCell + address, BottomRight + address);
        }
    }
}